/*===========================================================================
  Copyright (C) 2013 by the Okapi Framework contributors
-----------------------------------------------------------------------------
  This library is free software; you can redistribute it and/or modify it 
  under the terms of the GNU Lesser General Public License as published by 
  the Free Software Foundation; either version 2.1 of the License, or (at 
  your option) any later version.

  This library is distributed in the hope that it will be useful, but 
  WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
  General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License 
  along with this library; if not, write to the Free Software Foundation, 
  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

  See also the full LGPL text here: http://www.gnu.org/copyleft/lesser.html
===========================================================================*/

package net.sf.okapi.lib.xliff2.document;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Stack;

import net.sf.okapi.lib.xliff2.Util;
import net.sf.okapi.lib.xliff2.core.MidFileData;
import net.sf.okapi.lib.xliff2.core.Skeleton;
import net.sf.okapi.lib.xliff2.core.StartFileData;
import net.sf.okapi.lib.xliff2.reader.Event;
import net.sf.okapi.lib.xliff2.reader.EventType;
import net.sf.okapi.lib.xliff2.reader.URIContext;

/**
 * Represents a &lt;file> node.
 */
public class FileNode extends BeforeAndAfter {

	private StartFileData startData;
	private Skeleton skelData;
	private MidFileData midData;
	private LinkedHashMap<String, GroupOrUnitNode> nodes;

	/**
	 * Creates a new {@link FileNode} object with a given {@link StartFileData} resource.
	 * @param data the resource to attach to this node.
	 */
	public FileNode (StartFileData data) {
		this.startData = data;
		nodes = new LinkedHashMap<String, GroupOrUnitNode>();
	}
	
	/**
	 * Gets the {@link StartFileData} object for this file node.
	 * @return the {@link StartFileData} object for this file node.
	 */
	public StartFileData getStartData () {
		return startData;
	}
	
	/**
	 * Sets the {@link MidFileData} resource for this file node.
	 * @param data the {@link MidFileData} object for this file node.
	 */
	public void setMidData (MidFileData data) {
		this.midData = data;
	}
	
	/**
	 * Gets the {@link MidFileData} resource for this file node. 
	 * @return the {@link MidFileData} for this file node.
	 */
	public MidFileData getMidData () {
		return midData;
	}
	
	/**
	 * Sets the {@link Skeleton} resource for this file node.
	 * @param data the {@link Skeleton} object for this file node.
	 */
	public void setSkeletonData (Skeleton data) {
		this.skelData = data;
	}
	
	/**
	 * Gets the {@link Skeleton} for this file node.
	 * @return the {@link Skeleton} for this file node.
	 */
	public Skeleton getSkeletonData () {
		return skelData;
	}

	/**
	 * Adds a {@link UnitNode} to this file node.
	 * @param node the unit node to add.
	 * @return the added unit node.
	 */
	public UnitNode add (UnitNode node) {
		nodes.put("u"+node.get().getId(), node);
		return node;
	}

	/**
	 * Adds a {@link GroupNode} to this file node.
	 * If the group has a no id, one is set automatically.
	 * @param node the group node to add.
	 * @return the added group node.
	 */
	public GroupNode add (GroupNode node) {
		String id = node.get().getId();
		if ( id == null ) id = Util.createNCName();
		nodes.put("g"+id, node);
		return node;
	}
	
	/**
	 * Gets the {@link UnitNode} for a given unit id.
	 * The unit can be at any level in this file node.
	 * @param id the id to look for.
	 * @return the unit node for the given id, or null if none is found.
	 */
	public UnitNode getUnitNode (String id) {
		// Try at this level
		UnitNode item = (UnitNode)nodes.get("u"+id);
		if ( item != null ) return item;
		// Else: try recursively
		for ( GroupOrUnitNode node : nodes.values() ) {
			if ( !node.isUnit() ) {
				item = ((GroupNode)node).getUnitNode(id);
				if ( item != null ) return item;
			}
		}
		// Not found
		return null;
	}
	
	/**
	 * Gets a {@link GroupNode} for a given id.
	 * The group can be at any level in this file node.
	 * @param id the id to look for.
	 * @return the group node for the given id, or null if none is found.
	 */
	public GroupNode getGroupNode (String id) {
		// Try at this level
		GroupNode item = (GroupNode)nodes.get("g"+id);
		if ( item != null ) return item;
		// Else: try recursively
		for ( GroupOrUnitNode node : nodes.values() ) {
			if ( !node.isUnit() ) {
				item = ((GroupNode)node).getGroupNode(id);
				if ( item != null ) return item;
			}
		}
		// Not found
		return null;
	}

	/**
	 * Create an iterator for the event for this file node.
	 * @param uriContext the URI context.
	 * @return a new iterator for the events in this file node.
	 */
	public Iterator<Event> createEventIterator (Stack<URIContext> uriContext) {
		EventIterator ei = new EventIterator () {

			private Iterator<GroupOrUnitNode> iter = nodes.values().iterator();
			private Iterator<Event> eventIter = null;
			private int state = 0; // start-file event
			
			@Override
			public boolean hasNext () {
				switch ( state ) {
				case 0: // start-file
					return true;
				case -1: // We are done
					return false;
				case 1: // Skeleton
					if ( skelData != null ) return true;
					// Else: try mid-file
					state = 2; // Fall thru
				case 2: // Mid-file
					if ( midData != null ) return true;
					// Else: move to entries
					state = 3;
					// And fall thru
				}
				
				if ( eventIter != null ) {
					if ( eventIter.hasNext() ) return true;
					// Else: This group is done
					eventIter = null;
					// Fall thru to next item in the file
				}
				if ( iter.hasNext() ) {
					return true;
				}
				// Else: no more entries in this file
				// last event is the end-file
				state = 4;
				return true;
			}
			
			@Override
			public Event next () {
				switch ( state ) {
				case 0: // start-file
					state = 1; // Next is skeleton
					uriContext.push(uriContext.peek().clone());
					uriContext.peek().setFileId(startData.getId());
					return new Event(EventType.START_FILE, uriContext.peek(), startData);
				case 1: // Skeleton
					state = 2; // next is mid-file
					return new Event(EventType.SKELETON, uriContext.peek(), skelData);
				case 2: // Mid-file
					state = 3; // Normal entries
					return new Event(EventType.MID_FILE, uriContext.peek(), midData);
				case 4: // end-file
					state = -1; // Nothing after that
					uriContext.pop();
					return new Event(EventType.END_FILE, null);
				}
				// Otherwise: state = 3: entries
				// Use the events iterator if available
				if ( eventIter != null ) {
					return eventIter.next();
				}
				// Otherwise, 
				GroupOrUnitNode node = iter.next();
				if ( node.isUnit() ) {
					uriContext.push(uriContext.peek().clone());
					uriContext.peek().setUnitId(((UnitNode)node).get().getId());
					Event event = new Event(EventType.TEXT_UNIT, uriContext.peek(), ((UnitNode)node).get());
					uriContext.pop();
					return event;
				}
				// Else: it's a group node
				eventIter = ((GroupNode)node).createEventIterator(uriContext);
				eventIter.hasNext(); // Call once to prime the iterator
				return eventIter.next();
			}
			
		};
		ei.setURIContext(uriContext);
		return ei;
	}

}
